/**
 * -----------------------------------
 *  Copyright (c) 2021-2023
 *  All rights reserved, Designed By www.linfeng.tech
 * 林风社交论坛商业版本请务必保留此注释头信息
 * 商业版授权联系技术客服	 QQ:  3582996245
 * 严禁分享、盗用、转卖源码或非法牟利！
 * 版权所有 ，侵权必究！
 * -----------------------------------
 */
package io.linfeng.modules.app.websocket.component;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import io.jsonwebtoken.Claims;
import io.linfeng.common.utils.DateUtil;
import io.linfeng.common.utils.SnowFlakeUtil;
import io.linfeng.modules.app.entity.ChatMessageEntity;
import io.linfeng.modules.app.entity.FriendEntity;
import io.linfeng.modules.app.entity.NoticeEntity;
import io.linfeng.modules.app.service.ChatMessageService;
import io.linfeng.modules.app.service.FriendService;
import io.linfeng.modules.app.service.NoticeService;
import io.linfeng.modules.app.utils.JwtUtils;
import io.linfeng.modules.app.websocket.constant.MessageConstant;
import io.linfeng.modules.app.websocket.entity.SocketMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 消息通信核心业务模块
 *
 * @author linfeng
 * @date 2022/11/16 11:08
 */
@Slf4j
@Component
@ServerEndpoint("/app/socket/{token}")
public class SocketServer {

    public static Map<String, Session> userSessionMap = new ConcurrentHashMap<>();

    public static JwtUtils jwtUtils;

    public static ChatMessageService chatMessageService;

    public static NoticeService noticeService;

    public static FriendService friendService;

    @Autowired
    public void setChatMessageService(ChatMessageService chatMessageService) {
        SocketServer.chatMessageService = chatMessageService;
    }

    @Autowired
    public void setFriendService(FriendService friendService) {
        SocketServer.friendService = friendService;
    }

    @Autowired
    public void setJwtUtils(JwtUtils jwtUtils) {
        SocketServer.jwtUtils = jwtUtils;
    }

    @Autowired
    public void setNoticeService(NoticeService noticeService) {
        SocketServer.noticeService = noticeService;
    }


    @OnOpen
    public void onOpen(Session session, @PathParam("token") String token) {
        String id;
        try {
            Claims claims = jwtUtils.getClaimByToken(token);
            if (claims == null) {
                throw new Exception("身份验证失败");
            }
            id = claims.getSubject();
        } catch (Exception e) {
            this.sendMessage(new SocketMessage<>(MessageConstant.TOKEN_FAILED, null), session);
            return;
        }
        userSessionMap.put(id, session);
        log.info("用户:{}上线", id);
        this.sendToAll(new SocketMessage<>(MessageConstant.COUNT, userSessionMap.keySet().toArray()));
    }

    @OnClose
    public void onClose(Session session, @PathParam("token") String token) {
        Claims claims = jwtUtils.getClaimByToken(token);
        if (claims == null) {
            return;
        }
        String id = claims.getSubject();
        userSessionMap.remove(id);
        log.info("用户:{}离线", id);
        this.sendToAll(new SocketMessage<>(MessageConstant.COUNT, userSessionMap.keySet().toArray()));

    }

    @OnError
    public void onError(Session session, Throwable error, @PathParam("token") String token) {
        Claims claims = jwtUtils.getClaimByToken(token);
        if (claims == null) {
            return;
        }
        String id = claims.getSubject();
        userSessionMap.remove(id);
        log.info("用户:{}离线", id);
        this.sendToAll(new SocketMessage<>(MessageConstant.COUNT, userSessionMap.keySet().toArray()));

    }

    @OnMessage
    public void onMessage(String message, Session session, @PathParam("token") String token) {
        JSONObject msg = JSONUtil.parseObj(message);
        JSONObject data = msg.getJSONObject("data");
        switch (msg.getStr("type")) {
            /*接收心跳**/
            case MessageConstant.PING:
                break;
            /*私聊消息**/
            case MessageConstant.PERSON_MESSAGE:
                ChatMessageEntity chatMessage = ChatMessageEntity.builder()
                        .sessionId(data.getStr("sessionId"))
                        .senderId(data.getStr("senderId"))
                        .receiverId(data.getStr("receiverId"))
                        .sendTime(data.getStr("sendTime"))
                        .content(data.getStr("content"))
                        .messageType(data.getStr("messageType"))
                        .isWithdrawn(0)
                        .updateTime(DateUtil.nowDateTime())
                        .build();
                chatMessageService.saveMessage(chatMessage);
                this.sendToUserById(chatMessage.getSenderId(), new SocketMessage<>(MessageConstant.PERSON_MESSAGE, chatMessage));
                this.sendToUserById(chatMessage.getReceiverId(), new SocketMessage<>(MessageConstant.PERSON_MESSAGE, chatMessage));
                break;
            /*私聊撤回消息**/
            case MessageConstant.PERSON_WITHDRAW:
                String messageId = data.getStr("messageId");
                String senderId = data.getStr("senderId");
                String receiverId = data.getStr("receiverId");
                chatMessageService.withdrawMessage(messageId);
                this.sendToUserById(senderId, new SocketMessage<>(MessageConstant.PERSON_WITHDRAW, data));
                this.sendToUserById(receiverId, new SocketMessage<>(MessageConstant.PERSON_WITHDRAW, data));
                break;
            /*申请好友消息**/
            case MessageConstant.PERSON_APPLY:
                NoticeEntity notice = NoticeEntity.builder()
                        .senderId(data.getLong("senderId"))
                        .receiverId(data.getLong("receiverId"))
                        .type(MessageConstant.PERSON_APPLY)
                        .information(msg.getStr("data"))
                        .isRead(false)
                        .createTime(DateUtil.nowDateTime())
                        .updateTime(DateUtil.nowDateTime())
                        .build();
                noticeService.save(notice);
                this.sendToUserById(data.getStr("receiverId"), new SocketMessage<>(MessageConstant.PERSON_APPLY, msg.getStr("data")));
                break;
            /*申请好友通过消息**/
            case MessageConstant.PERSON_APPLY_AGREE:
                NoticeEntity noticeInfo = noticeService.getById(data.getStr("id"));
                JSONObject jsonObject = JSONUtil.parseObj(noticeInfo.getInformation());
                Boolean isFriend = friendService.checkIsFriend(noticeInfo.getReceiverId().intValue(), noticeInfo.getSenderId().intValue());
                if (isFriend) {
                    noticeInfo.setIsRead(true);
                    noticeService.saveOrUpdate(noticeInfo);
                    this.sendToUserById(noticeInfo.getReceiverId().toString(), new SocketMessage<>(MessageConstant.NOTICE_REFRESH, "TA已经是你的好友啦"));
                    break;
                }
                Long sessionId = SnowFlakeUtil.getSnowFlakeId();
                FriendEntity friend1 = FriendEntity.builder()
                        .myId(noticeInfo.getSenderId())
                        .friendId(noticeInfo.getReceiverId())
                        .notation(jsonObject.getStr("notation"))
                        .sessionId(sessionId)
                        .lastMessage(MessageConstant.DEFAULT_LAST_MESSAGE)
                        .unread(MessageConstant.NOT_READ)
                        .isHidden(false)
                        .createTime(DateUtil.nowDateTime())
                        .updateTime(DateUtil.nowDateTime())
                        .build();
                FriendEntity friend2 = FriendEntity.builder()
                        .myId(noticeInfo.getReceiverId())
                        .friendId(noticeInfo.getSenderId())
                        .notation(jsonObject.getStr("senderName"))
                        .sessionId(sessionId)
                        .lastMessage(MessageConstant.DEFAULT_LAST_MESSAGE)
                        .unread(MessageConstant.NOT_READ)
                        .isHidden(false)
                        .createTime(DateUtil.nowDateTime())
                        .updateTime(DateUtil.nowDateTime())
                        .build();
                friendService.save(friend1);
                friendService.save(friend2);
                noticeInfo.setIsRead(true);
                noticeService.saveOrUpdate(noticeInfo);
                SocketMessage<FriendEntity> socketMessage = new SocketMessage<>(MessageConstant.PERSON_APPLY_AGREE, friend2);
                this.sendToUserById(noticeInfo.getSenderId().toString(), socketMessage);
                this.sendToUserById(noticeInfo.getReceiverId().toString(), socketMessage);
                break;

        }

    }

    private void sendToAll(SocketMessage<?> message) {
        for (Session session : userSessionMap.values()) {
            this.sendMessage(message, session);
        }
    }

    private void sendToUserById(String id, SocketMessage<?> message) {
        for (String key : userSessionMap.keySet()) {
            if (Objects.equals(key, id)) {
                this.sendMessage(message, userSessionMap.get(key));
                return;
            }
        }
    }

    private void sendMessage(SocketMessage<?> message, Session toSession) {
        try {
            if (toSession.isOpen()) {
                synchronized (toSession) {
//                    toSession.getAsyncRemote().sendText(JSONUtil.toJsonStr(message));
                    toSession.getBasicRemote().sendText(JSONUtil.toJsonStr(message));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
